﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using SAM.Framework.Repository.Entities;

namespace SAM.Framework.Repository.Repositories
{
    /// <summary>
    /// base class to implement 
    /// abstract method
    /// 1. two Delete Methods
    /// 2. one GetAll Method
    /// 3. one Insert Method
    /// 4. one Update Method
    /// </summary>
    public abstract class RepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity:class, IEntity<TPrimaryKey>
    {
        public virtual int Count()
        {
            return BaseCount();
        }

        public int Count(Expression<Func<TEntity, bool>> predicate)
        {
            return BaseCount(predicate);
        }

        public Task<int> CountAsync()
        {
            
            return this.WrapFromResult(Count());
        }

        public Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return this.WrapFromResult(Count(predicate));
        }

        public abstract void Delete(TEntity entity);

        public abstract void Delete(TPrimaryKey id);

        public void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            foreach (var entity in GetAll().Where(predicate).ToList())
            {
                Delete(entity);
            }
        }

        public virtual Task DeleteAsync(TEntity entity)
        {
            Delete(entity);
            return this.WrapFromResult(0);
        }

        public Task DeleteAsync(TPrimaryKey id)
        {
            Delete(id);
            return this.WrapFromResult(0);
        }

        public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            Delete(predicate);
            return this.WrapFromResult(0);
        }

        public virtual TEntity FirstOrDefault(TPrimaryKey id)
        {
            return QueryLinq(w=>w.Id.Equals(id)).FirstOrDefault();
        }

        public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return QueryLinq(predicate).FirstOrDefault();
        }

        public Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        { 
            return this.WrapFromResult(FirstOrDefault(id));
        }

        public Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return this.WrapFromResult(FirstOrDefault(predicate));
        }

        public TEntity Get(TPrimaryKey id)
        {
            var entity = FirstOrDefault(id);
            if(entity == null)
            {
                throw new Exception();
            }
            return entity;
        }

        public abstract IQueryable<TEntity> GetAll();

        public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            throw new NotImplementedException();
        }

        public List<TEntity> GetAllList()
        {
            return GetAll().ToList();
        }

        public virtual List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
        {
            return QueryLinq(predicate).ToList();
        }

        public Task<List<TEntity>> GetAllListAsync()
        {
            return this.WrapFromResult(GetAllList());
        }

        public Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return this.WrapFromResult(GetAllList(predicate));
        }

        public Task<TEntity> GetAsync(TPrimaryKey id)
        {
            var entity = FirstOrDefaultAsync(id);
            if(entity == null)
            {
                throw new Exception();
            }
            return entity;
        }
        public abstract IEnumerable<TEntity> QueryLinq(Expression<Func<TEntity, bool>> predicate);
        public abstract IEnumerable<T> QuerySql<T>();

        public abstract int QueryCount(string sql,IDictionary<string,object> param);
        public abstract int QueryCount(string sql);
        public abstract int BaseCount(Expression<Func<TEntity, bool>> predicate);
        public abstract int BaseCount();
        public abstract IEnumerable<T> QuerySql<T>(string sql, IDictionary<string, object> param);
        public abstract TEntity Insert(TEntity entity);

        public TPrimaryKey InsertAndGetId(TEntity entity)
        {
            return Insert(entity).Id;
        }

        public Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            return this.WrapFromResult(InsertAndGetId(entity));
        }

        public Task<TEntity> InsertAsync(TEntity entity)
        {
            return this.WrapFromResult(Insert(entity));
        }

        public TEntity InsertOrUpdate(TEntity entity)
        {
            return entity.IsTransient()
                ? Insert(entity)
                : Update(entity);
        }

        public TPrimaryKey InsertOrUpdateAndGetId(TEntity entity)
        {
            return InsertOrUpdate(entity).Id;
        }

        public Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity)
        {
#if NET40
            return TaskExtend.FromResult(InsertOrUpdateAndGetId(entity));
#else
            return Task.FromResult(InsertOrUpdateAndGetId(entity));

#endif
        }

        public Task<TEntity> InsertOrUpdateAsync(TEntity entity)
        {
            return entity.IsTransient() ? InsertAsync(entity) : UpdateAsync(entity);
        }

        public TEntity Load(TPrimaryKey id)
        {
            return Get(id);
        }

        public long LongCount()
        {
            return GetAll().LongCount();
        }

        public long LongCount(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().Where(predicate).LongCount();
        }

        public Task<long> LongCountAsync()
        {
            return this.WrapFromResult(LongCount());
        }

        public Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return this.WrapFromResult(LongCount());
        }

        public T Query<T>(Func<IQueryable<TEntity>, T> queryMethod)
        {
            return queryMethod(GetAll());
        }

        public TEntity Single(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().Single(predicate);
        }

        public Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return this.WrapFromResult(Single(predicate));
        }

        public abstract TEntity Update(TEntity entity);

        public TEntity Update(TPrimaryKey id, Action<TEntity> updateAction)
        {
            var entity = Get(id);
            updateAction(entity);
            return entity;
        }

        public Task<TEntity> UpdateAsync(TEntity entity)
        {
            
            return this.WrapFromResult(Update(entity));
        }

#if NET40
#else
        public virtual async Task<TEntity> UpdateAsync(TPrimaryKey id, Func<TEntity, Task> updateAction)
        {
            var entity = await GetAsync(id);
            await updateAction(entity);
            return entity;
    }
#endif

        protected static Expression<Func<TEntity,bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));
            var lambdaBody = Expression.Equal(
                    Expression.PropertyOrField(lambdaParam, "Id"),
                    Expression.Constant(id, typeof(TPrimaryKey))
                );
            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
        private Task<T> WrapFromResult<T>(T entity)
        {
#if NET40
            return TaskExtend.FromResult(entity);
#else
            return Task.FromResult(entity);
#endif
        }
    }
#if NET40
    public static class TaskExtend
    {
        public static Task<TEntity> FromResult<TEntity>(TEntity entity)
        {
            var result = Task.Factory.StartNew(()=> {
                return entity;
            });
            return result;
        }
    }
#endif
}
